#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <string>
#include <map>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <iostream>
#include <chrono>
#include <future>
#include <pthread.h>
#include "../Negotiation/ECDHMethods.h"
#include "../Negotiation/OutputMethods.h"
#include "../Negotiation/RandomMethods.h"
#include "../Negotiation/HashMethods.h"
#include "../Buffer/InfoBuffer.h"
#include "../Utils/ConvertUtil.h"
#include "../Utils/JsonUtil.h"
#include "../CallBacker/Encrypt/3DES.h"

#define MYPORT 3490
#define BACKLOG 10
#define MAXDATASIZE 1000
using namespace std;

class ReceivingMsgDealer{
protected:
	ReceivingMsgDealer();
public:
	static pthread_mutex_t mutex;
    	static ReceivingMsgDealer* get_instance();
	void run();
	void sendMsg(const string s);
	string receiveMsg();
	void get_OneRTTnegotiation();
	void get_ZeroRTTnegotiation();
	void get_encryptNegotiation();
	void get_encrypt();
private:
	int sockfd;
	int new_fd;
	int numbytes;
	clock_t start, finish;
	double duration;
	char buf[MAXDATASIZE];
	struct sockaddr_in my_addr;
	struct sockaddr_in their_addr;
	socklen_t sin_size;
	static ReceivingMsgDealer *receiver;
	void initialize();
};
//making this object singleton
pthread_mutex_t ReceivingMsgDealer:: mutex;
ReceivingMsgDealer* ReceivingMsgDealer::receiver=NULL;

ReceivingMsgDealer::ReceivingMsgDealer(){
	pthread_mutex_init(&mutex,NULL);
	this->initialize();
}

ReceivingMsgDealer* ReceivingMsgDealer::get_instance()
{
	if(receiver==NULL)
	{
		pthread_mutex_lock(&mutex);
		if(receiver==NULL)
			receiver=new ReceivingMsgDealer();
		pthread_mutex_unlock(&mutex);
	}
	return receiver;
}

void ReceivingMsgDealer::initialize(){
	if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
		perror("socket error");
		exit(1);
	}
	my_addr.sin_family=AF_INET;
	my_addr.sin_port=htons(MYPORT);
	my_addr.sin_addr.s_addr=INADDR_ANY;
	bzero(&(my_addr.sin_zero),sizeof(my_addr.sin_zero));
	if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
		perror("bind error");
		exit(1);
	}
	if(listen(sockfd,BACKLOG)==-1){
		perror("listen error");
		exit(1);
	}
}
void  ReceivingMsgDealer::sendMsg(const string s){
	if(!fork()){
		if(send(new_fd,s.c_str(),s.size(),0)==-1)
			perror("send error");
		close(new_fd);
		exit(0);
	}
}

string ReceivingMsgDealer::receiveMsg(){
	char buf[MAXDATASIZE];
	if((numbytes=recv(new_fd,buf,MAXDATASIZE,0))==-1){
		perror("recv");
		exit(1);
	}
	buf[numbytes]='\0';
	string str=ConvertUtil::c_to_string(buf,numbytes);
	return str;
}
void ReceivingMsgDealer::run(){
	while(1){
		sin_size=sizeof(struct sockaddr_in);
		if((new_fd=accept(sockfd,(struct sockaddr *)&their_addr,&sin_size))==-1){
			perror("accept");
			continue;
		}
		//printf("Server:got connection from %s\n",inet_ntoa(their_addr.sin_addr));
		start=clock();
		string type=receiveMsg();
		if(type=="negotiation_One"){
			sendMsg("OK");
			get_OneRTTnegotiation();
		}
		else if(type=="negotiation_Zero")
		{
			get_ZeroRTTnegotiation();
		}
		else if(type=="negotiation_Encrypt")
		{
			sendMsg("OK");
			get_encryptNegotiation();
		}
		else if(type=="encrypt")
		{
			sendMsg("OK");
			get_encrypt();
		}

		while(waitpid(-1,NULL,WNOHANG)>0);
	}
}

void ReceivingMsgDealer::get_OneRTTnegotiation(){
	//initialize
	ECDHMethods ecdhMethods;
	OutputMethods outputMethods;
	HashMethods hash;
	RandomMethods rand;
	//ecdh key exchange
	unsigned char *keydata = (unsigned char *)malloc(ECDH_SIZE);
	EC_KEY *ecdh = ecdhMethods.genECDHtemppubkey(keydata);
	string pubK2=receiveMsg();
	string pubK=ConvertUtil::uc_to_string(keydata,ECDH_SIZE);
	sendMsg(pubK);
	unsigned char *keydata2=(unsigned char*)pubK2.c_str();
	unsigned char *ECDH_keydata2 =ecdhMethods.genECDHsharedsecret(ecdh, keydata2, ECDH_SIZE-1);
	string key=ConvertUtil::uc_to_string(ECDH_keydata2,ECDH_SIZE-1);
	string keyBase64=outputMethods.Base64Encode(key.c_str(),key.size(), true);

	InfoBuffer *buffer=InfoBuffer::get_instance();
	buffer->putEncryptKey(key);
	buffer=NULL;
	//psk hashing authentication
	string clientRandom=receiveMsg();
	string serverRandom=rand.random();
	string clipers=keyBase64+clientRandom+serverRandom;
	//cout<<keyBase64<<endl;
	string mac=hash.getMD5HashVal(clipers.c_str());
	map<string,string> clipers_map;
	clipers_map.insert(make_pair("server_random",serverRandom));
	clipers_map.insert(make_pair("mac",mac));
	string json_clipers=JsonUtil::map_to_json(clipers_map);
	sendMsg(json_clipers);
}

void ReceivingMsgDealer::get_ZeroRTTnegotiation()
{
	//initialize
	ECDHMethods ecdhMethods;
	OutputMethods output;
	HashMethods hash;
	RandomMethods rand;
	//generate static key
	unsigned char *pub_sever_static = (unsigned char *)malloc(ECDH_SIZE);
	EC_KEY *pri_sever_static = ecdhMethods.genECDHtemppubkey(pub_sever_static);
	string pub_sever_static_str=ConvertUtil::uc_to_string(pub_sever_static,ECDH_SIZE);
	sendMsg(pub_sever_static_str);
	//receiving client_random,app_data and pub_client
	string clipers=receiveMsg();
	map<string,string> clipers_map=JsonUtil::json_to_map(clipers);
	//string pub_client_str=clipers_map["pub_client"];
	string clientRandom=clipers_map["client_random"];
	string appData=clipers_map["appData"];
	std::chrono::milliseconds dur1(10);
              std::this_thread::sleep_for(dur1);
	string pub_client_str=receiveMsg();
	//generate SS key
	unsigned char *pub_client=(unsigned char *)pub_client_str.c_str();
	unsigned char *SS=ecdhMethods.genECDHsharedsecret(pri_sever_static,pub_client,ECDH_SIZE-1);
	string SS_str=ConvertUtil::uc_to_string(SS,ECDH_SIZE-1);
	string SS_base64=output.Base64Encode(SS_str.c_str(),SS_str.size(),true);
	cout<<"SS_base64:"<<SS_base64<<endl;
	//prepare ES key
	unsigned char *pub_sever= (unsigned char *)malloc(ECDH_SIZE);
	EC_KEY *pri_sever= ecdhMethods.genECDHtemppubkey(pub_sever);
	string pub_sever_str=ConvertUtil::uc_to_string(pub_sever,ECDH_SIZE);
	// hashing authentication
	string severRandom=rand.random();
	string hashClipers=clientRandom+severRandom+pub_sever_str;
	string mac=hash.getMD5HashVal(hashClipers.c_str());
	map<string,string> mac_map;
	mac_map.insert(make_pair("sever_random",severRandom));
	mac_map.insert(make_pair("pub_sever",pub_sever_str));
	mac_map.insert(make_pair("mac",mac));
	string json_clipers=JsonUtil::map_to_json(mac_map);
	sendMsg(json_clipers);
	//generate ES key
	std::chrono::milliseconds dur(10);
              std::this_thread::sleep_for(dur);
	sendMsg(pub_sever_str);
	unsigned char *ES=ecdhMethods.genECDHsharedsecret(pri_sever,pub_client,ECDH_SIZE-1);
	string ES_str=ConvertUtil::uc_to_string(ES,ECDH_SIZE-1);
	string ES_base64=output.Base64Encode(ES_str.c_str(),ES_str.size(),true);
	cout<<"ES_base64:"<<ES_base64<<endl;
	
}

void ReceivingMsgDealer:: get_encryptNegotiation()
{
	string clipers=receiveMsg();
	map<string,string> clipers_map=JsonUtil::json_to_map(clipers);
	sendMsg("host has received clipers");
}

void ReceivingMsgDealer::get_encrypt()
{
	string len_str=receiveMsg();
	std::chrono::milliseconds dur(10);
              std::this_thread::sleep_for(dur);
	int len=atoi(len_str.c_str());
	InfoBuffer *buffer=InfoBuffer::get_instance();
	string key=buffer-> getEncryptKey();
	buffer=NULL;
	string key24="1234567890";
	if(key!="")
		key24=key.substr(0,24);
	TDES des;
	sendMsg("ok");
	string d_src=receiveMsg();
	//cout<<"key24:"<<key24<<endl;
	string d_cry=des.TDES_decrypt(d_src,key24,len);
	cout<<d_cry<<endl;
}
